unit AVThread;

interface

uses SysUtils, Classes, Generics.Collections, Windows, AVUtils, AVTypes,
     AVGenerics, Rtti;

type
  TAVThread = class(TThread)
  private
    fOnBeginExecute: TNotifyEvent;
    fOnEndExecute: TNotifyEvent;
    fTerminateEvent: THandle;
    fOnTerminateAsinc: TNotifyEvent;
    procedure Execute; override;
  protected
    procedure Run; virtual; abstract;
    procedure TerminatedSet; override;
    procedure DoTerminate; override;
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    function WaitFor(H: THandle; TimeOut: DWORD = INFINITE): Boolean;

    property OnBeginExecute: TNotifyEvent read fOnBeginExecute write fOnBeginExecute;
    property OnEndExecute: TNotifyEvent read fOnEndExecute write fOnEndExecute;
    property Terminated;
    property OnTerminateAsinc: TNotifyEvent read fOnTerminateAsinc write fOnTerminateAsinc;
  end;

  TAVPoolThread = class;
  TAVThreadPool = class;

  TAVPoolThreadClassMethod = procedure(Thread: TAVPoolThread) of object;
  TAVPoolThreadMethod = procedure(Thread: TAVPoolThread);
  TAVPoolThreadClassCallback = procedure(Thread: TAVPoolThread) of object;
  TAVPoolThreadCallback = procedure(Thread: TAVPoolThread);

  PTAVPoolThreadQueueData = ^TAVPoolThreadQueueData;
  TAVPoolThreadQueueData = record
    ClassMethod: TAVPoolThreadClassMethod;
    Method: TAVPoolThreadMethod;
    Data: TObject;
    Name: string;
    Group: string;
    Cancel: boolean;
    ClassCallback: TAVPoolThreadClassCallback;
    Callback: TAVPoolThreadCallback;
    procedure Free;
    function HasJob: Boolean;
    procedure DoJob(Thread: TAVPoolThread);
    procedure DoCallback(Thread: TAVPoolThread);
  end;

  TAVPoolThread = class(TAVThread)
  protected
    fOnBeginJob: TNotifyEvent;
    fOnEndJob: TNotifyEvent;
    fOnNoJob: TNotifyEvent;
    fJob: TAVPoolThreadQueueData;
    fSyncInstance: TObject;
    fSyncInstanceMethod: string;
    fSyncMethodParams: array of TValue;
    fSyncMethodResult: TValue;
    procedure SynchronizeInstanceMethod;
    procedure Run; override;
    procedure TerminatedSet; override;
  public
    destructor Destroy; override;
    function Synchronize(SyncInstanceMethod: string; params: array of TValue): TValue; overload;

    property OnBeginJob: TNotifyEvent read fOnBeginJob write fOnBeginJob;
    property OnEndJob: TNotifyEvent read fOnEndJob write fOnEndJob;
    property OnNoJob: TNotifyEvent read fOnNoJob write fOnNoJob;
    property Job: TAVPoolThreadQueueData read fJob write fJob;
    property SyncInstance: TObject read fSyncInstance write fSyncInstance;
  end;

  TAVLockableObject = class(TObject)
  private
    fLock: TRTLCriticalSection;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Lock;
    procedure UnLock;
  end;

  TAVThreadQueue<T> = class(TAVLockableObject)
  private
    fQueue: TAVQueue<T>;
    fDataEvent: THandle;
    fOnClearItem: TAction<T>;
    function GetCount: integer;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;

    procedure Enqueue(const Value: T);
    function Dequeue(var Value: T): Boolean;
    procedure RestoreEvent;

    property Handle: THandle read fDataEvent;
    property OnClearItem: TAction<T> read fOnClearItem write fOnClearItem;
    property Count: integer read GetCount;
    property Queue: TAVQueue<T> read fQueue;
  end;

  TAVMonitorThread = class(TAVThread)
  protected
    pool: TAVThreadPool;
    fLastCreate: Cardinal;
    fLastDelete: Cardinal;
  protected
    constructor Create(APool: TAVThreadPool);
    procedure Run; override;
  end;

  TAVThreadPool = class(TAVLockableObject)
  private
    fThreads: TList<TAVPoolThread>;
    fMonitorThread: TAVMonitorThread;
    fMonitorThreadActive: boolean;
    fQueue: TAVThreadQueue<TAVPoolThreadQueueData>;
    fMinThreadCount: integer;
    fMaxThreadCount: integer;
    fActiveThreadCount: integer;
    fReadyThreadCount: integer;
    fFreeOneThread: boolean;
    fCanStartNewThread: boolean;
    procedure OnClearQueueItem(item: TAVPoolThreadQueueData);
    procedure SetMinThreadCount(Value: integer);
    function GetMeJob(Caller: TAVPoolThread): TAVPoolThreadQueueData;
    procedure CreateNewThread;
    procedure ThreadBeginExecute(Sender: TObject);
    procedure ThreadEndExecute(Sender: TObject);
    procedure ThreadBeginJob(Sender: TObject);
    procedure ThreadEndJob(Sender: TObject);
    procedure ThreadNoJob(Sender: TObject);
    procedure ThreadOnTerminateAsinc(Sender: TObject);
    procedure MonitorThreadBeginExecute(Sender: TObject);
    procedure MonitorThreadEndExecute(Sender: TObject);
    procedure AddJob(JobData: TAVPoolThreadQueueData); overload;
    procedure TerminateAll;
    procedure ClearQueue;
    function GetQueueCount: integer;
    procedure Wait;
  public
    constructor Create;
    destructor Destroy; override;
    procedure Clear;
    procedure CancelGroup(Group: string; WithWait: boolean = true);
    procedure AddJob(Method: TAVPoolThreadClassMethod; ThreadData: TObject; Group: string = ''; Name: string = ''; Callback: TAVPoolThreadClassMethod = nil); overload;
    procedure AddJob(Method: TAVPoolThreadMethod; ThreadData: TObject; Group: string = ''; Name: string = ''; Callback: TAVPoolThreadMethod = nil); overload;

    procedure AddJob(Method: TAVPoolThreadClassMethod; Group: string = ''; Name: string = ''; Callback: TAVPoolThreadClassMethod = nil); overload;
    procedure AddJob(Method: TAVPoolThreadMethod; Group: string = ''; Name: string = ''; Callback: TAVPoolThreadMethod = nil); overload;

    property MinThreadCount: integer read fMinThreadCount write SetMinThreadCount;
    property MaxThreadCount: integer read fMaxThreadCount write fMaxThreadCount;
    property ActiveThreadCount: integer read fActiveThreadCount;
    property ReadyThreadCount: integer read fReadyThreadCount;
    property QueueCount: integer read GetQueueCount;
  end;

  TAVThreadContext = class(TObject)
  private
    fGroup: string;
    function ValidGroup(g: string; trimLastAsterisk: boolean = false): string;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Cancel(SubGroup: string);
    procedure CancelAll;

    procedure AddJob(Method: TAVPoolThreadClassMethod; ThreadData: TObject; SubGroup: string = ''; Name: string = ''; Callback: TAVPoolThreadClassMethod = nil); overload;
    procedure AddJob(Method: TAVPoolThreadMethod; ThreadData: TObject; SubGroup: string = ''; Name: string = ''; Callback: TAVPoolThreadMethod = nil); overload;

    procedure AddJob(Method: TAVPoolThreadClassMethod; SubGroup: string = ''; Name: string = ''; Callback: TAVPoolThreadClassMethod = nil); overload;
    procedure AddJob(Method: TAVPoolThreadMethod; SubGroup: string = ''; Name: string = ''; Callback: TAVPoolThreadMethod = nil); overload;
  end;

function AVThreadPool: TAVThreadPool;
function AVThreadCount: integer;

implementation

var
  fAVThreadPool: TAVThreadPool = nil;
  fAVThreadCount: integer = 0;

function AVThreadPool: TAVThreadPool;
begin
  if fAVThreadPool = nil then
    fAVThreadPool := TAVThreadPool.Create;
  Result := fAVThreadPool;
end;

function AVThreadCount: integer;
begin
  Result := fAVThreadCount;
end;

// TAVThread

procedure TAVThread.Execute;
begin
  if Assigned(fOnBeginExecute) then
    fOnBeginExecute(Self);
  try
    Run;
  finally
    if Assigned(fOnEndExecute) then
      fOnEndExecute(Self);
  end;
end;

constructor TAVThread.Create(CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);
  fTerminateEvent := CreateEvent(nil, true, false, PWideChar(GuidToString(NewGuid)));
  InterlockedIncrement(fAVThreadCount);
end;

destructor TAVThread.Destroy;
begin
  inherited Destroy;
  CloseHandle(fTerminateEvent);
  InterlockedDecrement(fAVThreadCount);
end;

procedure TAVThread.TerminatedSet;
begin
  PulseEvent(fTerminateEvent);
end;

procedure TAVThread.DoTerminate; 
begin
  if Assigned(fOnTerminateAsinc) then fOnTerminateAsinc(Self);
  inherited DoTerminate;
end;

function TAVThread.WaitFor(H: THandle; TimeOut: DWORD = INFINITE): Boolean;
var
  HArr: array [0 .. 1] of THandle;
  WaitResult: Cardinal;
begin
  HArr[0] := fTerminateEvent;
  HArr[1] := H;
  WaitResult := MsgWaitForMultipleObjects(2, HArr, false, TimeOut, QS_SENDMESSAGE);
  Result := WaitResult = WAIT_OBJECT_0 + 1;
end;

// TAVPoolThread

destructor TAVPoolThread.Destroy; 
begin
  inherited Destroy;
end;

procedure TAVPoolThread.Run;
begin
  while not Terminated do
  begin
    fJob := AVThreadPool.GetMeJob(Self);
    try
      if not Terminated and fJob.HasJob then
      begin
        if Assigned(fOnBeginJob) then fOnBeginJob(Self);
        try
          try
            fJob.DoJob(Self);
          finally
            fJOb.DoCallback(Self);
          end;
        finally
          if Assigned(fOnEndJob) then fOnEndJob(Self);
        end;
      end else
      if not Terminated then begin //NoJob
        if Assigned(fOnNoJob) then fOnNoJob(Self);
      end;
    finally
      fJob.Free;
    end;
  end;
end;

procedure TAVPoolThread.TerminatedSet;
begin
  fJob.Cancel := true;
  inherited TerminatedSet;
end;

procedure TAVPoolThread.SynchronizeInstanceMethod;
begin
  fSyncMethodResult := TAVRttiObj(fSyncInstance).Invoke(fSyncInstanceMethod, fSyncMethodParams);
end;

function TAVPoolThread.Synchronize(SyncInstanceMethod: string; params: array of TValue): TValue;
var i: integer;
begin
  fSyncInstanceMethod := SyncInstanceMethod;

  SetLength(fSyncMethodParams, length(params));
  for i := 0 to length(params) - 1 do
    fSyncMethodParams[i] := params[i];

  Synchronize(SynchronizeInstanceMethod);
  Result := fSyncMethodResult;
end;

// TAVPoolThreadQueueData

procedure TAVPoolThreadQueueData.Free;
begin
  if Data <> nil then
    FreeAndNil(Data);
  ClassMethod := nil;
  Method := nil;
  Name := '';
  Group := '';
  ClassCallback := nil;
  Callback := nil;
  Cancel := false;
end;

function TAVPoolThreadQueueData.HasJob: Boolean;
begin
  Result := Assigned(ClassMethod) or Assigned(Method);
end;

procedure TAVPoolThreadQueueData.DoJob(Thread: TAVPoolThread);
begin
  if Assigned(ClassMethod) then
    ClassMethod(Thread)
  else if Assigned(Method) then
    Method(Thread);
end;

procedure TAVPoolThreadQueueData.DoCallback(Thread: TAVPoolThread);
begin
  if Assigned(ClassCallback) then
    ClassCallback(Thread)
  else if Assigned(Callback) then
    Callback(Thread);
end;

// TAVLockableObject

constructor TAVLockableObject.Create;
begin
  InitializeCriticalSection(fLock);
  inherited Create;
end;

destructor TAVLockableObject.Destroy;
begin
  inherited Destroy;
  DeleteCriticalSection(fLock);
end;

procedure TAVLockableObject.Lock;
begin
  EnterCriticalSection(fLock);
end;

procedure TAVLockableObject.UnLock;
begin
  LeaveCriticalSection(fLock);
end;

// TAVThreadQueue<T>

constructor TAVThreadQueue<T>.Create;
begin
  inherited Create;
  fQueue := TAVQueue<T>.Create;
  fDataEvent := CreateEvent(nil, true, false, PWideChar(GuidToString(NewGuid)));
end;

destructor TAVThreadQueue<T>.Destroy;
begin
  Clear;
  fQueue.Free;
  CloseHandle(fDataEvent);
  inherited Destroy;
end;

procedure TAVThreadQueue<T>.Clear;
var
  qData: T;
begin
  Lock;
  try
    while fQueue.Count > 0 do
    begin
      qData := fQueue.Dequeue;
      if Assigned(fOnClearItem) then
        fOnClearItem(qData);
    end;
    ResetEvent(fDataEvent);
  finally
    UnLock;
  end;
end;

procedure TAVThreadQueue<T>.Enqueue(const Value: T);
begin
  Lock;
  try
    fQueue.Enqueue(Value);
    SetEvent(fDataEvent);
  finally
    UnLock;
  end;
end;

function TAVThreadQueue<T>.Dequeue(var Value: T): Boolean;
begin
  Lock;
  try
    if fQueue.Count > 0 then
    begin
      Value := fQueue.Dequeue;
      Result := true;
    end
    else
    begin
      Result := false;
    end;

    if fQueue.Count > 0
      then SetEvent(fDataEvent)
      else ResetEvent(fDataEvent);
  finally
    UnLock;
  end;
end;

procedure TAVThreadQueue<T>.RestoreEvent;
begin
  Lock;
  try
    if fQueue.Count > 0 then
      SetEvent(fDataEvent)
    else
      ResetEvent(fDataEvent);
  finally
    UnLock;
  end;
end;

function TAVThreadQueue<T>.GetCount: integer;
begin
  Result := fQueue.Count;
end;

//TAVMonitorThread

constructor TAVMonitorThread.Create(APool: TAVThreadPool);
begin
  inherited Create(true);
  FreeOnTerminate := true;
  pool := APool;
end;

procedure TAVMonitorThread.Run;
var isQueue: boolean; 
begin
  while not Terminated do begin
    isQueue := WaitFor(pool.fQueue.Handle, 100);
    if Terminated then break;
    pool.fQueue.RestoreEvent;

    if (isQueue) and (pool.fThreads.Count < pool.MaxThreadCount) and (pool.ReadyThreadCount < 1) then begin
      fLastCreate := GetTickCount;
      pool.CreateNewThread;
      if pool.fThreads.Count > pool.MinThreadCount then Sleep(10) else
      if pool.fQueue.Count < 3 then Sleep(2);                                                            
    end else begin
      Sleep(10);
      if (pool.fThreads.Count > pool.MinThreadCount) and 
         (pool.ReadyThreadCount > Trunc(pool.fThreads.Count*2/3)) and
         (abs(fLastCreate - GetTickCount) > 5000) and
         (abs(fLastDelete - GetTickCount) > 50)  
      then begin
        fLastDelete := GetTickCount;
        pool.Lock;
        try
          pool.fFreeOneThread := true;
        finally
          pool.UnLock;
        end;
      end;
    end;
  end;
end;

// TAVThreadPool

constructor TAVThreadPool.Create;
begin
  inherited Create;
  fThreads := TList<TAVPoolThread>.Create;
  fQueue := TAVThreadQueue<TAVPoolThreadQueueData>.Create;
  fQueue.OnClearItem := OnClearQueueItem;
  fMaxThreadCount := 200;
  fMonitorThread := TAVMonitorThread.Create(Self);
  fMonitorThread.OnBeginExecute := MonitorThreadBeginExecute;
  fMonitorThread.OnEndExecute := MonitorThreadEndExecute;

  MinThreadCount := 2;
  fCanStartNewThread := true;
  fMonitorThread.Start;
end;

destructor TAVThreadPool.Destroy;
begin
  Clear;

  fMonitorThread.Terminate;
  while fMonitorThreadActive do Sleep(1);
  
  fThreads.Free;
  fQueue.Free;
  inherited Destroy;
end;

procedure TAVThreadPool.Clear;
begin
  ClearQueue;
  TerminateAll;
  ClearQueue;
end;

procedure TAVThreadPool.CancelGroup(Group: string; WithWait: boolean = true);
type
  TMatchType = (equal, start, contains);
var
  i: integer;
  GroupExist: boolean;
  matchType: TMatchType;

  procedure CheckMatchType;
  begin
    if Pos('*', Group) = 1 then begin
      Group := Copy(Group, 2, length(Group) - 1);
      matchType := contains;
      if Pos('*', Group) = length(Group)
        then Group := Copy(Group, 1, length(Group) - 1);
    end else
    if Pos('*', Group) = length(Group) then begin
      Group := Copy(Group, 1, length(Group) - 1);
      matchType := start;
    end else matchType := equal;
  end;

  function GroupMatch(JobGroup: string): boolean;
  begin
    case matchType of
      equal: Result := JobGroup = Group;
      start: Result := Pos(Group, JobGroup) = 1;
      contains: Result := Pos(Group, JObGroup) > 0;
      else
        Result := false;
    end;
  end;

begin
  CheckMatchType;

  Lock;
  fQueue.Lock;
  try
    for i := 0  to fQueue.Count - 1 do begin
      if GroupMatch(fQueue.Queue.PItems[i]^.Group)
       then fQueue.Queue.PItems[i]^.Cancel := true;
    end;

    for i := 0 to fThreads.Count - 1 do
      if GroupMatch(fThreads[i].fJob.Group) then fThreads[i].fJob.Cancel := true;
  finally
    fQueue.UnLock;
    UnLock;
  end;

  if WithWait then begin
    repeat
      Wait;
      GroupExist := false;
      Lock;
      try
        for i := 0 to fThreads.Count - 1 do
          if GroupMatch(fThreads[i].fJob.Group) then begin
            GroupExist := true;
            Break;
          end;
      finally
        UnLock;
      end;
    until not GroupExist;
  end;
end;

procedure TAVThreadPool.MonitorThreadBeginExecute(Sender: TObject);
begin
  fMonitorThreadActive := true;
end;

procedure TAVThreadPool.MonitorThreadEndExecute(Sender: TObject);
begin
  fMonitorThreadActive := false;
end;

procedure TAVThreadPool.OnClearQueueItem(item: TAVPoolThreadQueueData);
begin
  item.Free;
end;

procedure TAVThreadPool.Wait;
begin
  if TThread.CurrentThread.ThreadID = MainThreadID
    then CheckSynchronize(5);
  Sleep(5);
end;

procedure TAVThreadPool.TerminateAll;
var
  i: integer;
  empty: boolean;
begin
  Lock;
  try
    fCanStartNewThread := false;
    for i := 0 to fThreads.Count - 1 do
    begin
      fThreads[i].Terminate;
    end;
  finally
    UnLock;
  end;

  repeat
    empty := fThreads.Count = 0;
    if not empty then Wait;
  until (empty);
  fCanStartNewThread := true;
end;

procedure TAVThreadPool.ClearQueue;
begin
  fQueue.Clear;
end;

function TAVThreadPool.GetQueueCount: integer;
begin
  Result := fQueue.Count;
end;

procedure TAVThreadPool.SetMinThreadCount(Value: integer);
begin
  fMinThreadCount := Value;
end;

function TAVThreadPool.GetMeJob(Caller: TAVPoolThread): TAVPoolThreadQueueData;
begin
  Result.Data := nil;
  Result.Free;
  Result.Cancel := true;
  if Caller.WaitFor(fQueue.Handle, 200) then
  begin
    while fQueue.Dequeue(Result) and Result.Cancel do Result.Free;
  end;
  fQueue.RestoreEvent;
end;

procedure TAVThreadPool.CreateNewThread;
var
  th: TAVPoolThread;
begin
  Lock;
  try
    if fCanStartNewThread then begin
      th := TAVPoolThread.Create(true);
      fThreads.Add(th);
      th.OnBeginExecute := ThreadBeginExecute;
      th.OnEndExecute := ThreadEndExecute;
      th.OnBeginJob := ThreadBeginJob;
      th.OnEndJob := ThreadEndJob;
      th.OnNoJob := ThreadNoJob;
      th.OnTerminateAsinc := ThreadOnTerminateAsinc;
      th.FreeOnTerminate := true;
      th.Start;
    end;
  finally
    UnLock;
  end;
end;

procedure TAVThreadPool.ThreadBeginExecute(Sender: TObject);
begin
  Lock;
  try
    Inc(fActiveThreadCount);
    Inc(fReadyThreadCount);
  finally
    UnLock;
  end;
end;

procedure TAVThreadPool.ThreadEndExecute(Sender: TObject);
begin
  Lock;
  try
    Dec(fReadyThreadCount);
    Dec(fActiveThreadCount);
  finally
    UnLock;
  end;
end;

procedure TAVThreadPool.ThreadOnTerminateAsinc(Sender: TObject);
begin
  Lock;
  try
    fThreads.Remove(TAVPoolThread(Sender));
  finally
    UnLock;
  end;
end;

procedure TAVThreadPool.ThreadBeginJob(Sender: TObject);
begin
  Lock;
  try
    Dec(fReadyThreadCount);
  finally
    UnLock;
  end;
end;

procedure TAVThreadPool.ThreadEndJob(Sender: TObject);
begin
  Lock;
  try
    Inc(fReadyThreadCount);
    if fFreeOneThread then 
    begin
      fFreeOneThread := false;
      TAVPoolThread(Sender).Terminate;
    end; 
  finally
    UnLock;
  end;
end;

procedure TAVThreadPool.ThreadNoJob(Sender: TObject);
begin
  Lock;
  try
    if fFreeOneThread then
    begin
      fFreeOneThread := false;
      TAVPoolThread(Sender).Terminate;
    end;
  finally
    UnLock;
  end;
end;

procedure TAVThreadPool.AddJob(JobData: TAVPoolThreadQueueData);
begin
  fQueue.Enqueue(JobData);
end;

procedure TAVThreadPool.AddJob(Method: TAVPoolThreadClassMethod;
  ThreadData: TObject; Group: string = ''; Name: string = ''; Callback: TAVPoolThreadClassMethod = nil);
var
  JobData: TAVPoolThreadQueueData;
begin
  JobData.ClassMethod := Method;
  JobData.Method := nil;
  JobData.Data := ThreadData;
  JobData.Group := Group;
  JobData.Name := Name;
  JobData.Cancel := false;
  JobData.ClassCallback := Callback;
  JobData.Callback := nil;
  AddJob(JobData);
end;

procedure TAVThreadPool.AddJob(Method: TAVPoolThreadMethod;
  ThreadData: TObject; Group: string = ''; Name: string = ''; Callback: TAVPoolThreadMethod = nil);
var
  JobData: TAVPoolThreadQueueData;
begin
  JobData.ClassMethod := nil;
  JobData.Method := Method;
  JobData.Data := ThreadData;
  JobData.Group := Group;
  JobData.Name := Name;
  JobData.Cancel := false;
  JobData.ClassCallback := nil;
  JobData.Callback := Callback;
  AddJob(JobData);
end;

procedure TAVThreadPool.AddJob(Method: TAVPoolThreadClassMethod; Group: string = ''; Name: string = ''; Callback: TAVPoolThreadClassMethod = nil);
begin
  AddJob(Method, nil, Group, Name, Callback);
end;

procedure TAVThreadPool.AddJob(Method: TAVPoolThreadMethod; Group: string = ''; Name: string = ''; Callback: TAVPoolThreadMethod = nil);
begin
  AddJob(Method, nil, Group, Name, Callback);
end;

//TAVThreadContext

constructor TAVThreadContext.Create;
begin
  inherited Create;
  fGroup := GUIDToString(NewGuid);
end;

destructor TAVThreadContext.Destroy;
begin
  AVThreadPool.CancelGroup(fGroup + '*');
  inherited Destroy;
end;

procedure TAVThreadContext.Cancel(SubGroup: string);
begin
  AVThreadPool.CancelGroup(ValidGroup(SubGroup) + '*');
end;

procedure TAVThreadContext.CancelAll;
begin
  Cancel('*');
end;

function TAVThreadContext.ValidGroup(g: string; trimLastAsterisk: boolean = false): string;
begin
  if Pos(fGroup + '.', g) = 1
    then Result := g
    else Result := fGroup + '.' + g;

  if trimLastAsterisk then
  while (length(Result) > 0) and (Result[length(Result)] = '*') do
    Result := Copy(Result, 1, length(Result) - 1);
end;

procedure TAVThreadContext.AddJob(Method: TAVPoolThreadClassMethod; ThreadData: TObject; SubGroup: string = ''; Name: string = ''; Callback: TAVPoolThreadClassMethod = nil);
begin
  AVThreadPool.AddJob(Method, ThreadData, ValidGroup(SubGroup), Name, Callback);
end;

procedure TAVThreadContext.AddJob(Method: TAVPoolThreadMethod; ThreadData: TObject; SubGroup: string = ''; Name: string = ''; Callback: TAVPoolThreadMethod = nil);
begin
  AVThreadPool.AddJob(Method, ThreadData, ValidGroup(SubGroup), Name, Callback);
end;

procedure TAVThreadContext.AddJob(Method: TAVPoolThreadClassMethod; SubGroup: string = ''; Name: string = ''; Callback: TAVPoolThreadClassMethod = nil);
begin
  AVThreadPool.AddJob(Method, ValidGroup(SubGroup), Name, Callback);
end;

procedure TAVThreadContext.AddJob(Method: TAVPoolThreadMethod; SubGroup: string = ''; Name: string = ''; Callback: TAVPoolThreadMethod = nil);
begin
  AVThreadPool.AddJob(Method, ValidGroup(SubGroup), Name, Callback);
end;

initialization

finalization
  if fAVThreadPool <> nil then FreeAndNil(fAVThreadPool);

end.
